package com.gitee.sparta.core.tempalte;

import cn.hutool.core.collection.CollUtil;
import com.gitee.sparta.api.SpartaTemplate;
import com.gitee.sparta.common.SpartaConstant;
import com.gitee.sparta.common.helpers.DurationHelper;
import com.gitee.sparta.common.helpers.RedisKeyHelper;
import com.gitee.sparta.core.bean.metadata.Metadata;
import com.gitee.sparta.core.process.operate.AbstractTimeoutTabOperate;
import com.gitee.sparta.core.process.operate.DenyTabOperate;
import com.gitee.sparta.core.process.operate.MetadataTabOperate;
import com.gitee.sparta.core.property.SpartaCoreProperties;
import lombok.AllArgsConstructor;

import java.time.Duration;
import java.util.*;

/**
 * sparta 提供了对任务的操作 api 实现
 * <p>
 *
 *  @author zyred
 * @since v 0.0.1
 */
@AllArgsConstructor
public class SpartaTemplateImpl<V> implements SpartaTemplate<V> {

    private final SpartaCoreProperties properties;
    private final DenyTabOperate<V> denyTabOperate;
    private final AbstractTimeoutTabOperate<V> timeoutTabOperate;
    private final MetadataTabOperate<V> metadataTabOperate;


    @Override
    public void write(String topic, V val, long expire) {
        this.timeoutTabOperate.write(null, topic, val, expire, false);
    }

    @Override
    public void write(String metaId, String topic, V val, long expire) {
        this.timeoutTabOperate.write(metaId, topic, val, expire, false);
    }

    @Override
    public void writeAsync(String topic, V val, long expire) {
        this.timeoutTabOperate.writeAsync(null, topic, val, expire, false);
    }

    @Override
    public void writeAsync(String metaId, String topic, V val, long expire) {
        this.timeoutTabOperate.writeAsync(metaId, topic, val, expire, false);
    }

    @Override
    public void write(String topic, V val, Set<String> timeNodes) {
        Set<Duration> times = DurationHelper.parseDuration(timeNodes);
        this.timeoutTabOperate.write(null, topic, val, new ArrayList<>(times), false);
    }

    @Override
    public void write(String metaId, String topic, V val, Set<String> timeNodes) {
        Set<Duration> times = DurationHelper.parseDuration(timeNodes);
        this.timeoutTabOperate.write(metaId, topic, val, new ArrayList<>(times), false);
    }

    @Override
    public void write(String topic, V val, List<Duration> timeNodes) {
        this.timeoutTabOperate.write(null, topic, val, timeNodes, false);
    }

    @Override
    public void write(String metaId, String topic, V val, List<Duration> timeNodes) {
        this.timeoutTabOperate.write(metaId, topic, val, timeNodes, false);
    }

    @Override
    public void writeAsync(String topic, V val, Set<String> timeNodes) {
        Set<Duration> times = DurationHelper.parseDuration(timeNodes);
        this.timeoutTabOperate.writeAsync(null, topic, val, new ArrayList<>(times), false);
    }

    @Override
    public void writeAsync(String metaId, String topic, V val, Set<String> timeNodes) {
        Set<Duration> times = DurationHelper.parseDuration(timeNodes);
        this.timeoutTabOperate.writeAsync(metaId, topic, val, new ArrayList<>(times), false);
    }

    @Override
    public void writeAsync(String topic, V val, List<Duration> timeNodes) {
        this.timeoutTabOperate.writeAsync(null, topic, val, timeNodes, false);
    }

    @Override
    public void writeAsync(String metaId, String topic, V val, List<Duration> timeNodes) {
        this.timeoutTabOperate.writeAsync(metaId, topic, val, timeNodes, false);
    }

    @Override
    public void writeBroadcast(String channel, V val, long expire) {
        this.timeoutTabOperate.write(null, channel, val, expire, true);
    }

    @Override
    public void writeBroadcast(String metaId, String channel, V val, long expire) {
        this.timeoutTabOperate.write(metaId, channel, val, expire, true);
    }

    @Override
    public void writeBroadcastAsync(String channel, V val, long expire) {
        this.timeoutTabOperate.writeAsync(null, channel, val, expire, true);
    }

    @Override
    public void writeBroadcastAsync(String metaId, String channel, V val, long expire) {
        this.timeoutTabOperate.writeAsync(metaId, channel, val, expire, true);
    }

    @Override
    public List<V> processDenys() {
        final String denyTabKey = RedisKeyHelper.tabKey(this.properties.getSystemName(), SpartaConstant.TABLE_DENY_NAME);
        final String metadataTabKey = RedisKeyHelper.tabKey(this.properties.getSystemName(), SpartaConstant.TABLE_METADATA_NAME);

        List<V> messages = this.denyTabOperate.processDenys(denyTabKey, metadataTabKey);
        if (CollUtil.isEmpty(messages)) {
            return Collections.emptyList();
        }
        return messages;
    }

    @Override
    public V select(String topic, String metaId) {
        Metadata<V> metadata = selectMetadata(topic, metaId);
        return Objects.nonNull(metadata) ? metadata.getBody() : null;
    }

    /**
     * 查询 metadata
     *
     * @param topic     topic
     * @param metaId    metaId
     * @return          metadata
     */
    private Metadata<V> selectMetadata (String topic, String metaId) {
        final String field = RedisKeyHelper.fieldName(topic, metaId);
        return this.metadataTabOperate.readMetadata(field);
    }

    @Override
    public V delete(String topic, String metaId) {
        final String field = RedisKeyHelper.fieldName(topic, metaId);
        Metadata<V> metadata = this.selectMetadata(topic, metaId);
        if (Objects.isNull(metadata)) {
            return null;
        }

        final String timeoutTabKey = RedisKeyHelper.tabKey(this.properties.getSystemName(), SpartaConstant.TABLE_TIMEOUT_NAME);
        final String metadataTabKey = RedisKeyHelper.tabKey(this.properties.getSystemName(), SpartaConstant.TABLE_METADATA_NAME);
        final String readableTabKey = RedisKeyHelper.readableKey(this.properties.getSystemName(), this.properties.getIsolation(), topic);

        String segmentFields = "";
        if (metadata.isSegment()) {
            List<String> fields = new ArrayList<>();
            for (int i = 0, len = metadata.getSegmentSize(); i < len; i++) {
                fields.add(field + SpartaConstant.LINE + i);
            }
            segmentFields = String.join(SpartaConstant.COMMA, fields);
        }
        this.metadataTabOperate.delete(readableTabKey, timeoutTabKey, metadataTabKey, segmentFields, field);
        return metadata.getBody();
    }
}
